#include <stdio.h>

#include <inject.h>
#include <envs.h>

#include <game_p.h>

typedef char *(*wgufn_t)(wchar_t* path); // wine_get_unix_file_name

const wchar_t *J_MB_TITLE = L"Jadeite Launcher Payload";

BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
    // Only listen for attach
    if (reason != DLL_PROCESS_ATTACH) {
        return TRUE;
    }

    // Get target EXE path
    wchar_t target_exe[MAX_PATH];
    GetEnvironmentVariableW(ENV_EXE_PATH, target_exe, MAX_PATH);
    
    // Get the path of the DLL to inject
    wchar_t inject_dll[MAX_PATH];
    GetEnvironmentVariableW(ENV_DLL_PATH, inject_dll, MAX_PATH);

    // Get game commandline
    wchar_t cmdline[8192];
    GetEnvironmentVariableW(ENV_PROC_CMD, cmdline, sizeof(cmdline) / sizeof(wchar_t));

    // Compute the working directory path
    wchar_t workdir[MAX_PATH];
    wcscpy(workdir, target_exe);
    *(wcsrchr(workdir, L'\\')) = L'\0';

    // SAFETY: verify that the injector is not inside the game directory
    HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
    wgufn_t wine_get_unix_file_name = (wgufn_t)GetProcAddress(kernel32, "wine_get_unix_file_name");

    if (wine_get_unix_file_name) {
        char *unix_inject_dll = wine_get_unix_file_name(inject_dll);
        if (!unix_inject_dll) {
            MessageBoxW(NULL, L"Invalid DLL path (internal injector error). Please open an issue on the jadeite repository", J_MB_TITLE, MB_OK | MB_ICONERROR);
            exit(1);
        }

        char *unix_workdir = wine_get_unix_file_name(workdir);
        if (!unix_workdir) {
            wchar_t message[1024];
            wsprintfW(message, L"Invalid target executable path: '%s'", target_exe);
            MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONERROR);
            exit(1);
        }

        char *i = unix_inject_dll, *w = unix_workdir;
        char starts_with = 0;
        while (*i && *w) {
            starts_with = *i == *w;
            if (!starts_with) break;

            i++, w++;
        }

        HANDLE heap = GetProcessHeap();
        HeapFree(heap, 0, unix_inject_dll);
        HeapFree(heap, 0, unix_workdir);

        if (starts_with) {
            MessageBoxW(NULL, L"Putting the patcher (or any other foreign PE binaries) inside the game directory is dangerous! Please move it elsewhere.", J_MB_TITLE, MB_OK | MB_ICONERROR);
            exit(1);
        }
    } else {
        MessageBoxW(NULL, L"Could not find wine_get_unix_file_name! Wine version too old?", J_MB_TITLE, MB_OK | MB_ICONWARNING);
    }

    // Start the game
    STARTUPINFOW si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);

    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

    if (!CreateProcessW(
        NULL,
        cmdline,
        NULL,
        NULL,
        FALSE,
        CREATE_SUSPENDED,
        NULL,
        workdir,
        &si,
        &pi
    )) {
        wchar_t message[1024];
        wsprintfW(message, L"Failed to start game process: %ld\nGame executable path: '%ls'", GetLastError(), target_exe);
        MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONERROR);

        exit(1);
    }

    // Inject
    void *payload_start = &_binary_game_p_o_p_game_p_bin_start;
    size_t payload_size = (size_t)&_binary_game_p_o_p_game_p_bin_size;
    inject(pi.hProcess, payload_start, payload_size, inject_dll);

    // Optional: wait for user input before resuming (useful for debugging) 
    char *wait_enabled = getenv("WAIT_BEFORE_RESUME");
    if (wait_enabled && *wait_enabled) {
        wchar_t message[64];
        wsprintfW(message, L"PID: %ld. Press OK to continue", pi.dwProcessId);
        MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONINFORMATION);
    }

    // Resume the process
    ResumeThread(pi.hThread);

    // The launcher process should now hang untill the game terminates
    WaitForSingleObject(pi.hProcess, INFINITE);

    return TRUE;
}
